﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CitizenDiary.Sync.Contract;
using CitizenDiary.Sync.Shared;
using System.Data.SqlClient;
using System.ServiceModel;

namespace CitizenDiary.Sync.Service
{
    public class SyncService : ISyncService
    {
        private string _database = "CitizenDiary_Server";

        public HandshakeResponse Handshake(HandshakeRequest request)
        {
            var syncItems = new SynchronizationCollection();
            using (var conn = Helper.CreateConnection(_database))
            {
                using (var cmd = conn.CreateCommand())
                {
                    // retrieve the data on server side for future compare
                    // in production we should just retrieve the data in a time slot instead of all
                    var serverDiaryItems = new SynchronizationCollection();
                    cmd.CommandText = "SELECT * FROM DiaryItem";
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            serverDiaryItems.Add((Guid)reader["ID"], new SynchronizationItem(reader));
                        }
                    }

                    // check from the device data to the server data
                    foreach (var deviceDiaryItem in request.DeviceItems)
                    {
                        if (serverDiaryItems.ContainsKey(deviceDiaryItem.Key))
                        {
                            var serverDiaryItem = serverDiaryItems[deviceDiaryItem.Key];
                            // data exist on server
                            // should detect if we need to update to server, or to device, or it's a conflict
                            if (serverDiaryItem.UpdatedVersion == deviceDiaryItem.Value.UpdatedVersion && serverDiaryItem.UpdatedDeviceID == deviceDiaryItem.Value.UpdatedDeviceID)
                            {
                                // the updated version and device id are the same between server and device this means the data didn't change
                                syncItems.Add(
                                    deviceDiaryItem.Key,
                                    new SynchronizationItem() { Type = SynchronizationType.Ignore });
                            }
                            else
                            {
                                // the data was different between the server and device 
                                // if it had been changed on device we need to detect the conflict, otherwise just update the device data
                                if (deviceDiaryItem.Value.HasChanged())
                                {
                                    // change made on device 
                                    if (deviceDiaryItem.Value.BasedDeviceID == serverDiaryItem.UpdatedDeviceID && deviceDiaryItem.Value.BasedVersion == serverDiaryItem.UpdatedVersion)
                                    {
                                        // the device data's based version and device id are equals to the server
                                        // this means no change made before thie sync by other device 
                                        // so we can update this data to server from device
                                        syncItems.Add(
                                            deviceDiaryItem.Key,
                                            new SynchronizationItem() { Type = SynchronizationType.Post });
                                    }
                                    else
                                    {
                                        // the base information on device was different to the server
                                        // this means others had changed the data before this sync
                                        // it's a conflict since this deivce and other device are all changed this data
                                        syncItems.Add(
                                            deviceDiaryItem.Key,
                                            new SynchronizationItem(serverDiaryItem, SynchronizationType.Conflict));
                                    }
                                }
                                else
                                {
                                    // no change made on device
                                    // just download the server data and update to the device
                                    syncItems.Add(
                                        deviceDiaryItem.Key,
                                        new SynchronizationItem() { Type = SynchronizationType.Update });
                                }
                            }
                        }
                        else
                        {
                            // server doesn't contains the item that exist on device
                            // it need to be upload and insert to server
                            syncItems.Add(
                                deviceDiaryItem.Key,
                                new SynchronizationItem() { Type = SynchronizationType.Put });
                        }
                    }

                    // check from the server data to determine which data should be insert to device
                    foreach (var serverDiaryItem in serverDiaryItems)
                    {
                        if (!request.DeviceItems.ContainsKey(serverDiaryItem.Key))
                        {
                            syncItems.Add(
                                serverDiaryItem.Key,
                                new SynchronizationItem() { Type = SynchronizationType.Get });
                        }
                    }
                }
            }
            // todo: generate and save the token
            return new HandshakeResponse()
            {
                Token = string.Empty,
                SynchronizationItems = syncItems
            };
        }

        public SynchronizeResponse Synchronize(SynchronizeRequest request)
        {
            // todo: check the token

            var response = new SynchronizeResponse();
            using (var conn = Helper.CreateConnection(_database))
            {
                // start transaction for the sync operation
                var tran = conn.BeginTransaction();
                using (var cmd = conn.CreateCommand())
                {
                    cmd.Transaction = tran;
                    try
                    {
                        // based on the sync type to update the server data from device
                        foreach (var syncItem in request.SynchronizationItems)
                        {
                            switch(syncItem.Value.Type)
                            {
                                case SynchronizationType.Conflict:
                                    throw new FaultException(string.Format("The item (ID = {0}) is conflict type which is incorrect.", syncItem.Key));
                                case SynchronizationType.Post: // update to server
                                    cmd.CommandText = string.Format(
                                        "UPDATE DiaryItem SET Content = '{0}', UpdatedVersion = {1}, UpdatedDeviceID = '{2}', IsDeleted = '{4}' WHERE ID = '{3}'",
                                        syncItem.Value.Content,
                                        syncItem.Value.UpdatedVersion,
                                        syncItem.Value.UpdatedDeviceID,
                                        syncItem.Key,
                                        syncItem.Value.IsDeleted);
                                    cmd.ExecuteNonQuery();
                                    break;
                                case SynchronizationType.Put: // insert to server
                                    cmd.CommandText = string.Format(
                                        "INSERT INTO DiaryItem (ID, Content, UpdatedVersion, UpdatedDeviceID, IsDeleted) VALUES ('{0}', '{1}', {2}, '{3}', '{4}')",
                                        syncItem.Key,
                                        syncItem.Value.Content,
                                        syncItem.Value.UpdatedVersion,
                                        syncItem.Value.UpdatedDeviceID,
                                        syncItem.Value.IsDeleted);
                                    cmd.ExecuteNonQuery();
                                    break;
                                case SynchronizationType.Update: // update to local
                                case SynchronizationType.Get: // insert to local
                                    cmd.CommandText = string.Format("SELECT * FROM DiaryItem WHERE ID = '{0}'", syncItem.Key);
                                    using (var reader = cmd.ExecuteReader())
                                    {
                                        if (reader.Read())
                                        {
                                            response.SynchronizationItems.Add(syncItem.Key, new SynchronizationItem(reader, syncItem.Value.Type));
                                        }
                                    }
                                    break;
                                default: // ignore
                                    break;
                            }
                        }
                        tran.Commit();
                        // retrieve the max update version for this device
                        cmd.CommandText = string.Format("SELECT MAX(UpdatedVersion) FROM DiaryItem WHERE UpdatedDeviceID = '{0}'", request.DeviceID);
                        var maxUpdVersionObject = cmd.ExecuteScalar();
                        if (maxUpdVersionObject == null || maxUpdVersionObject == DBNull.Value)
                        {
                            response.MaxUpdateVersion = 0;
                        }
                        else
                        {
                            response.MaxUpdateVersion = (long)maxUpdVersionObject;
                        }
                    }
                    catch
                    {
                        if (tran != null)
                        {
                            tran.Rollback();
                        }
                        throw;
                    }
                }
            }

            return response;
        }

        //public HandshakeResponse Handshake(HandshakeRequest request)
        //{
        //    var response = new HandshakeResponse();
        //    using (var conn = Helper.CreateConnection(_database))
        //    {
        //        using (var cmd = conn.CreateCommand())
        //        {
        //            // step 1: check the items updated from the device to determine if they need to be uploaded or has conflict
        //            //         if found but the based information (local) equals the updated infomation (service) then means they need to be uploaded
        //            //                  otherwise, means there's conflict (something changed before the sync)
        //            //         if not found means this is a new item and need to be uploaded
        //            response.UploadCondidates = request.Items.Select(self =>
        //                {
        //                    var candidate = new HandshakeResponse.UploadCondidate() { ID = self.ID };
        //                    cmd.CommandText = string.Format("SELECT Content, UpdatedVersion, UpdatedDeviceID FROM DiaryItem WHERE ID = '{0}'", self.ID);
        //                    using (var reader = cmd.ExecuteReader())
        //                    {
        //                        if (reader.Read())
        //                        {
        //                            // found the same item on server side
        //                            var serviceContent = (string)reader["Content"];
        //                            var serviceUpdatedVersion = (long)reader["UpdatedVersion"];
        //                            var serviceUpdatedDeviceId = (Guid)reader["UpdatedDeviceID"];
        //                            if (serviceUpdatedVersion == self.UpdatedVersion && serviceUpdatedDeviceId == self.UpdatedDeviceID)
        //                            {
        //                                // the updated information are same between the server and device, no need to sync
        //                                candidate.Mode = HandshakeResponse.UploadMode.Ignore;
        //                            }
        //                            else
        //                            {
        //                                // the updated infomation are different, next to check if other device updated this item before this sync
        //                                if (serviceUpdatedDeviceId != self.BasedDeviceID || serviceUpdatedVersion != self.BasedVersion)
        //                                {
        //                                    // the based information is different, this means other device changed and sync-ed data before this sync
        //                                    if (self.UpdatedDeviceID == self.BasedDeviceID && self.UpdatedVersion == self.BasedVersion)
        //                                    {
                                                
        //                                        candidate.Mode = HandshakeResponse.UploadMode.Ignore;
        //                                    }
        //                                    else
        //                                    {
        //                                        // data have been changed in this device while had been changed as well from other device, this is a conflit
        //                                        candidate.Mode = HandshakeResponse.UploadMode.Conflict;
        //                                        candidate.ServerSideContent = serviceContent;
        //                                        candidate.ServerSideUpdatedDeviceID = serviceUpdatedDeviceId;
        //                                    }
        //                                }
        //                                else
        //                                {
        //                                    // no change made before this sync, just update the client side data
        //                                    candidate.Mode = HandshakeResponse.UploadMode.Update;
        //                                }
        //                            }
        //                        }
        //                        else
        //                        {
        //                            // doesn't found it on server side means this is new created on device
        //                            candidate.Mode = HandshakeResponse.UploadMode.Insert;
        //                        }
        //                    }
        //                    return candidate;
        //                })
        //                .ToList();
        //            //// step 2: check the items NOT updated from the device to determine they need to be updated from the server side, and is ther any new data on server side as well
        //            ////         if found and the updated version / device id equals to the one in this device, then no need to download (it's updated to date)
        //            ////                  otherwise means they need to be download from the service
        //            ////         if not found that means it's new items on server side, which means need to be downloaded
        //            //var downloads = new List<HandshakeResponse.DownloadItem>();
        //            //cmd.CommandText = string.Format("SELECT ID, Content, UpdatedVersion, UpdatedDeviceID FROM DiaryItem WHERE UpdatedDeviceID <> '{0}'", request.DeviceID);
        //            //using (var reader = cmd.ExecuteReader())
        //            //{
        //            //    while (reader.Read())
        //            //    {
        //            //        var id = (Guid)reader["ID"];
        //            //        var content = (string)reader["Content"];
        //            //        var updatedVersion = (long)reader["UpdatedVersion"];
        //            //        var updatedDeviceId = (Guid)reader["UpdatedDeviceID"];
        //            //        if (!request.SelfItems.Any(item => item.ID == id))
        //            //        {
        //            //            if (request.RestItems.ContainsKey(id))
        //            //            {
        //            //                var local = request.RestItems[id];
        //            //                if (local.UpdatedDeviceID != updatedDeviceId || local.UpdatedVersion != updatedVersion)
        //            //                {
        //            //                    downloads.Add(new HandshakeResponse.DownloadItem()
        //            //                    {
        //            //                        ID = id,
        //            //                        Content = content,
        //            //                        UpdatedDeviceID = updatedDeviceId,
        //            //                        UpdatedVersion = updatedVersion
        //            //                    });
        //            //                }
        //            //            }
        //            //            else
        //            //            {
        //            //                downloads.Add(new HandshakeResponse.DownloadItem()
        //            //                {
        //            //                    ID = id,
        //            //                    Content = content,
        //            //                    UpdatedDeviceID = updatedDeviceId,
        //            //                    UpdatedVersion = updatedVersion
        //            //                });
        //            //            }
        //            //        }
        //            //    }
        //            //}
        //            //response.DownloadItems = downloads;
        //        }
        //    }
        //    return response;
        //}

        //public TransactResponse Transact(TransactRequest request)
        //{
        //    var response = new TransactResponse();
        //    using (var conn = Helper.CreateConnection(_database))
        //    {
        //        using (var cmd = conn.CreateCommand())
        //        {
        //            // apply the items come from the device (update or insert)
        //            foreach (var upload in request.UploadItems)
        //            {
        //                if (upload.IsNew)
        //                {
        //                    cmd.CommandText = string.Format("INSERT INTO DiaryItem (ID, Content, UpdatedVersion, UpdatedDeviceID, IsDeleted) VALUES ('{0}', '{1}', {2}, '{3}', '{4}')",
        //                        upload.ID, upload.Content, upload.UpdatedVersion, upload.UpdatedDeviceID, upload.IsDeleted);
        //                }
        //                else
        //                {
        //                    cmd.CommandText = string.Format("UPDATE DiaryItem SET Content = '{0}', UpdatedVersion = {1}, UpdatedDeviceID = '{2}', IsDeleted = '{4}' WHERE ID = '{3}'",
        //                        upload.Content, upload.UpdatedVersion, upload.UpdatedDeviceID, upload.ID, upload.IsDeleted);
        //                }
        //                cmd.ExecuteNonQuery();
        //            }
        //            // check the items that were not updated by this device, compare with the items from the device
        //            // to determine which should be send to the device to update
        //            var downloads = new List<TransactResponse.DownloadItem>();
        //            cmd.CommandText = string.Format("SELECT * FROM DiaryItem WHERE UpdatedDeviceID <> '{0}'", request.DeviceID);
        //            using (var reader = cmd.ExecuteReader())
        //            {
        //                while (reader.Read())
        //                {
        //                    var id = (Guid)reader["ID"];
        //                    var content = (string)reader["Content"];
        //                    var updatedVersion = (long)reader["UpdatedVersion"];
        //                    var updatedDeviceId = (Guid)reader["UpdatedDeviceID"];
        //                    var isDeleted = (bool)reader["IsDeleted"];
        //                    if (request.RestItems.ContainsKey(id))
        //                    {
        //                        // this item is exist at the device side, need to check the update version and id to determine if need to update
        //                        var item = request.RestItems[id];
        //                        if (item.UpdatedDeviceID != updatedDeviceId || item.UpdatedVersion != updatedVersion)
        //                        {
        //                            downloads.Add(new TransactResponse.DownloadItem()
        //                            {
        //                                IsNew = false,
        //                                ID = id,
        //                                Content = content,
        //                                UpdatedDeviceID = updatedDeviceId,
        //                                UpdatedVersion = updatedVersion,
        //                                IsDeleted = isDeleted
        //                            });
        //                        }
        //                    }
        //                    else
        //                    {
        //                        // this is a new item to the device side, just send to the device and insert
        //                        downloads.Add(new TransactResponse.DownloadItem()
        //                            {
        //                                IsNew = true,
        //                                ID = id,
        //                                Content = content,
        //                                UpdatedDeviceID = updatedDeviceId,
        //                                UpdatedVersion = updatedVersion,
        //                                IsDeleted = isDeleted
        //                            });
        //                    }
        //                }
        //            }
        //            response.DownloadItems = downloads;
        //        }
        //    }
        //    return response;
        //}

        public PingResponse Ping(PingRequest request)
        {
            var response = new PingResponse();
            using (var conn = Helper.CreateConnection(_database))
            {
                using (var cmd = conn.CreateCommand())
                {
                    cmd.CommandText = string.Format(
                        "SELECT Device.ID AS DeviceID, Device.Name AS DeviceName, " +
                        "Member.ID AS MemberID, Member.Email AS MemberEmail, " +
                        "Application.ID AS ApplicationID, Application.Name AS ApplicationName " +
                        "FROM Device " +
                        "LEFT JOIN Member ON Device.MemberID = Member.ID " +
                        "LEFT JOIN Application ON Device.ApplicationID = Application.ID " +
                        "WHERE Device.ID = '{0}'",
                        request.DeviceID);
                    using (var reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            response.Device = new KeyValuePair<Guid, string>((Guid)reader["DeviceID"], (string)reader["DeviceName"]);
                            response.Member = new KeyValuePair<Guid, string>((Guid)reader["MemberID"], (string)reader["MemberEmail"]);
                            response.Application = new KeyValuePair<Guid, string>((Guid)reader["ApplicationID"], (string)reader["ApplicationName"]);
                        }
                    }
                }
            }
            return response;
        }
    }
}
